Программы
Книги
Статьи

27_img

МИКРОКОНТРОЛЛЕРЫ? ЭТО ЖЕ ПРОСТО!

Материал книги изложен исключительно грамотно. Она самодостаточна — начинающему не придется безуспешно искать в библиотеках издания, на которые обычно ссылаются при рассмотрении смежных вопросов (как то машинная арифметика, представление чисел и т. п.), в то же время опытный разработчик легко может пропустить знакомые разделы без ущерба для понимания.


Скачать книгу  

Описание

А. В. Фрунзе
МИКРОКОНТРОЛЛЕРЫ?
ЭТО ЖЕ ПРОСТО! том 1


о книге
Как известно, уже набила оскомину фраза про «все
возрастающий интерес инженерной общественности к
применению микроконтроллеров в задачах управления и
обработки сигналов». Литературы по упомянутой тематики
вроде бы стало издаваться достаточно. Тем не менее, выход
данной книги — событие. Почему же? Так потому, что это ПРО-
СТО! Лично меня трудно удивить литературной новинкой в
электронной области, но когда я взял в руки первую статью из
цикла, на базе которого написана эта книга, то испытал
приятный шок — наконец то на просторах 1/6 части суши
появилась книга, написанная на хорошем русском языке, не
лишенная чувства юмора и вместе с тем технически
исключительно цельная и грамотная. В общем, это и не удиви-
тельно. Александр Вилленович Фрунзе — известный и
авторитетный специалист, и хочется надеяться, что книгу,
которую Вы сейчас держите в руках, ждет долгая и счастливая
судьба. Я почти уверен, что и через 10—15 лет основные ее
положения будут оставаться актуальными. Это важное
отличие данной книги от великого множества изданий по
микроконтроллерной тематике, которые в огромном
большинстве представляют собой либо справочники, либо
компиляцию с фирменных руководств по конкретным
семействам.
Материал излагается на примере старого доброго 51-го
контроллера. Предвижу возмущение некоторых специалистов,
готовых похоронить этот контроллер из-за
«неперспективности, устаревшей архитектуры и системы
команд». Тем не менее, на основе этой архитектуры
выпускается огромное число различных контроллеров,
содержащих на борту широкую гамму
переферийныхустройств — от портов до АЦП, ЦАП и
интерфейса CAN. Кроме того ~~ как извест-


но, «кадры решают все». В стране выросло не одно поколение
разработчиков, активно применяющих 51-контроллеры, его
изучение введено в учебные планы вузов, и, наконец в
классическом исполнении его еще выпускает отечественная
промышленность. Из личного опыта могу сказать только одно
— надо использовать ту элементную базу, которой владеешь. К
сожалению, внедрение новых семейств связано с необходимостью
их освоения, а это время и, конечно же, деньги.
^Материал книги изложен исключительно грамотно. Она
самодостаточна — начинающему не придется безуспешно
искать в библиотеках издания, на которые обычно ссылаются при
рассмотрении смежных вопросов (как то машинная арифметика,
представление чисел и т. п.), в то же время опытный
разработчик легко может пропустить знакомые разделы без
ущерба для понимания. Изложение настолько последовательно и
выверено, что книга читается как хорошее художественное
произведение. Крайне разумно используются приложения — они
позволяют не перегрузить основной текст и в то же время это не
сухие справочные данные, а разделы, имеющие самостоятельную
ценность. Тут и описание работы с микроконверторалш от Analog
Devices, и исследование влияния на рынок средств поддержки
разработки, и пути модернизации систем — охват тем
широчайший! Причем и в основном тексте, и в приложениях
чувствуется авторский стиль — это действительно
настоящая работа.
Отдельно следует упомянуть о примерах из книги. Не
поленился и прогнал первый попавшийся наугад — PAJBOTAET!
Все примеры тщательно протестированы и помогут
начинающему в освоении нелегкого искусства разработки
систем на микроконтроллерах.
Конечно, как и в любой столь масштабной работе, не
обошлось без недостатков. Практически не рассматриваются
вопросы проектирования систем с несколькими параллельно
работающими контроллерами.
Особо следует сказать, что эту книгу можно и нужно
рекомендовать студентам. Она приучает к
самостоятельности мышления и дает представление о логике
и методах проектирования, что зачастую гораздо важнее
технических подробностей.
Рецензент доцент кафедры
«Автономные информационные
и управляющие системы»
МГТУ им. Н. Э. Баумана,
К.Т.Н.В.Б.СТЕШЕНКО


ПРЕДИСЛОВИЕ
Идея написания этой книги возникла у меня довольно неожиданно.
Так сложилось, что в течение одной-двух недель сразу четверо авторов
из тех, кто регулярно публикует свои материалы в журнале
«СХЕМОТЕХНИКА», в разговоре со мной коснулись темы полного
отсутствия литературы, по которой люди, не имеющие опыта работы с
микроконтроллерами, смогли бы освоить их. Примерно в это же время
я услышал подобные сетования и от двух моих знакомых инженеров-
электронщиков, специалистов в аналоговой электронике — они тоже
столкнулись с тем, что хотели бы освоить работу с
микроконтроллерами, но не представляют, где найти литературу,
рассчитанную на новичков, самостоятельно начинающих почти с нуля.
Мне казалось, что подобной литературы если не навалом, то во всяком
случае очень много, и просто нужно пару раз съездить в книжные
магазины, торгующие научно-технической литературой — от обилия
предлагаемых ими книг в них просто рябит в глазах, и не может быть,
чтобы выбрать было не из чего. Но когда я сам посмотрел на появивши-
еся в последние несколько лет книги по микроконтроллерной тематике,
а также публикации в журналах, я понял, что практически все они
ориентированы на тех, кто уже освоил эту предметную область. Статей
и книг, рассчитанных на новичков и позволяющих им шаг за шагом
освоить микроконтроллеры, не перегружая их раньше времени
важными, но необязательными в первый момент подробностями, увы,
нет. Так родилась идея написать для начинающих цикл статей по
микроконтроллерам, знакомство с которым позволило бы им осознать,
что такое микроконтроллеры, как они устроены, функционируют, как
писать, отлаживать и заносить в них профаммы, и т. д. Тем более, что в
свое время подобные книги были —достаточно вспомнить, например,
замечательную книгу Дж. Коффрона «Технические средства
микропроцессорных систем», вышедшую в 1983 г. в издательстве
«Мир» — с ней знакомы практически все отечественные специалисты
по микроконтроллерной технике, начинавшие в 80-х с незабвенного
КР580ИК80...
Первоначально я планировал просто написать цикл статей для
журнала «СХЕМОТЕХНИКА», полагая, что вряд ли стоит делать книгу
из материала, в основе которого лежат тысячу раз описанные
микроконтроллеры разработанного еще в конце 80-х годов прошлого
столетия семейства х51. Однако несколько десятков отзывов, которые
я получил от читателей в ходе публикации первых частей
подготовленного материала, и отклики авторов, имеющих опыт
издания книг, убедили меня в том, что подобная книга может быть
интересна широкому кругу читателей. Поэтому после публикации в
«СХЕМОТЕХНИКЕ» первых трех глав я решил, что этот материал
должен появиться в виде книги, и готовил его далее уже с учетом
принятого решения.
Несмотря на большой опыт написания самых разнообразных
статей, задачу подготовки книги для начинающих я поставил перед
собой впервые. Тем более, что книг аналогичного содержания, как я
уже отмечал, у нас пока еще нет. Поэтому мне было довольно сложно
принимать решения, какой материал может быть опущен при первом
знакомстве с микроконтроллерами, а без какого не обойтись.
Поскольку вследствие необходимости упрощения материала основной
части книги многие особенности рассматриваемых микро-
контроллеров мне пришлось опустить, я счел необходимым снабдить
книгу


большим количеством приложений, куда перенес опущенное.
Знакомство с приложениями не является необходимым при первом
чтении книги, но, безусловно, окажется полезным для тех, кто решит
идти дальше. При этом знакомиться с материалами приложений
можно в любой последовательности, по мере понимания приведенной
там информации и появления интереса к ней. Обратите также
внимание и на книге в списке рекомендуемой литературы —
некоторые вопросы в них освещены гораздо полнее, чем у меня, хотя
в силу особенности стиля, которым они написаны, начинающим я
рекомендовал бы знакомиться с ними лишь после того, как они
разберутся с существом рассматриваемой темы по материалам
настоящей книги.
Отдельно хочу сказать о программном обеспечении к настоящей
книге. Я в своей работе (пока еще) в основном использую старый
DOS-овский ассемблер TASM. В связи с этим все, что говорится мной
в Главе 2 о процедуре ассемблирования, относится именно к этому
ассемблеру. Соответственно, фрагменты программ и сами программы
также написаны на этом ассемблере. Все это (TASM и программы) вы
можете найти на сайте: http:// www.pyrometer.ru.
Для тех, кто уже работает с каким-либо другим ассемблером, при
использовании моих программ и подпрограмм может понадобиться
некоторое редактирование исходных ассемблерных текстов с учетом
особенностей их ассемблеров.
Далее необходимо отметить следующее. Все программы и их
фрагменты, приведенные в главах 1—8 книги, взяты из реальных,
написанных мной в разные годы программ. Однако при переносе их в
книгу и некотором «причесывании» могли возникнуть ошибки. Я
тщательно проверял тексты программ, но 100-процентно
гарантировать то, что в приведенных материалах ошибок нет, я все же
не рискнул бы. Поэтому если кто из вас обнаружит в моих программах
какую-либо ошибку, я очень прошу сообщить об этом мне
(alex.fru@mtu-net.ru), а также в редакцию журналов «КОМПОНЕНТЫ и
ТЕХНОЛОГИИ» и «СХЕМОТЕХНИКА». Информация об ошибках, если
таковые найдутся, будет оперативно отражаться в материалах,
помещенных на вышеупомянутом сайте.
Ряд материалов, вошедших в книгу, подготовлен не мной, а
другими авторами. Приложения 4 и 8 написаны Ю. Зобниным и Ш.
Кобахидзе, Приложение 5 — В. Мясниковым (все они — специалисты
московской фирмы «Фи-тон»). Материал Главы 8 и приложения 6
подготовлен моим сыном, Алексеем Фрунзе. Обзоры
микроконтроллерных семейств фирмы Cygnal (первая часть
Приложения 10) и Atmel (вторая часть Приложения 3) подготовлены О.
Ни-колайчуком (АО Informlnstrument, Кишинев, Молдова), информация
по ADuC812 и ADuC816 (Приложение 11) — А. Соловьевым и А.
Соболевым (фирма Аргуссофт», Москва). Все перечисленное я только
отредактировал. Эти материалы включены в книгу с устного согласия
авторов, и я хочу поблагодарить их за проделанную работу.
И в заключение я хотел бы отметить терпение моей жены
Татьяны, с пониманием относившейся к более чем полугодовым моим
вечерне-ночным бдениям над рукописью, без чего появление книги,
которую вы держите сейчас в руках, вряд ли было бы возможным.
Александр фрунЗЕ


ГЛАВА 1
ПЕРВОЕ ЗНАКОМСТВО
Как уже упоминалось в предисловии, в качестве объекта изучения я
выбрал микроконтроллеры (МК) семейства х51. Почему именно их?
Во-первых потому, что мне, автору, легче объяснять материал на
основе того, что я знаю лучше всего (AVR или Р1С, к примеру, я знаю
намного хуже). Во-вторых, все, кто разобрался хотя бы с одним
контроллером, после этого всегда в состоянии самостоятельно
разобраться с любым иным— было бы время и желание (или
необходимость). А в-третьих, с этими контроллерами по-прежнему
работает не меньше разработчиков, чем с AVR или Р1С-
контроллерами, не говоря уже о любых других, причем Cygnal, Atmel и
Analog Devices в последнее время предоставили в наше с вами
распоряжение еще более совершенные образцы контроллеров этого
семейства. Да и не только они — чтобы убедиться в этом, достаточно
заглянуть в приведенные в приложении обзоры, посвященные х51-со-
вместимым микроконтроллерам, выпускаемым более чем десятком ли-
деров мировой микроэлектроники. После знакомства с ними становит-
ся очевидно, что слухи о кончине славного 51-го семейства оказались
явно преувеличенными, и еще добрый десяток лет эти изделия будут
вполне конкурентоспособными в семействе наиболее
распространенных 8-разрядньЕх: микроконтроллеров. Так что
отбросим снобизм и начнем наше знакомство со старым и добрым МК
семейства х51.
ПАМЯТЬ МИКРОКОНТРОЛЛЕРА
Первое, с чего стоит начать — это со слов, что при всей кажущей-
ся сложности ничего непостижимого в микроконтроллерах нет. МК
представляют собой микросхемы, которые всего-навсего «от корки до
корки» выполняют программы, занесенные в них программиста-


ми. Последние, зная, что из себя представляет
микроконтроллер, а также то, как и какие команды он может
выполнить, составили и отладили программы
(последовательность этих самых команд), занесли их в
микроконтроллеры, и при подаче питания МК выполняют все то,
что было предусмотрено программистами.
Из сказанного выше вытекает ряд вопросов. Вопрос номер
один — микроконтроллер может выполнять какие-то команды.
Какие? Второй вопрос — программа пишется программистом и
заносится в МК. Как? А заодно, где она там хранится?
Начнем с последнего. У большинства МК имеется память
программ, представляющая из себя некоторое количество
ячеек (от тысячи до десятка тысяч и более). Она находится
внутри самой микросхемы (говорят — «на кристалле»). Каждая
ячейка имеет свой порядковый номер, или, как говорят
программисты, адрес. В этих ячейках хранятся числа, могущие
принимать значения от О до 255. Упомянутые числа и есть та
самая программа, которую выполняет микроконтроллер, если
мы подадим на него питание.
Удивлены*' На самом деле все довольно просто, хотя на
первый взгляд может показаться непривычным. Программа —
это последовательность выполняемых микроконтроллером
команд. Каждой команде в памяти программ соответствует свое
число (корректнее сказать — код). При включении питания МК
один за другим считывает эти коды, осуществляет их
дешифрацию (другими словами, определяет, что же нужно
сделать), а затем исполняет одну за другой эти
дешифрированные команды. Кстати, отмечу, что в
контроллерах семейства х51 первой выполняется команда, код
которой расположен в самой первой ячейке памяти программ с
адресом 0.
Главная особенность памяти программ — занесенные в нее
коды сохраняются неизменными при отсутствии питания у
микроконтроллера. Это, в общем, и очевидно — уж коль скоро
мы написали и отладили программу, она не должна
самопроизвольно изменяться с течением времени.
Память программ — это не единственный вид памяти,
имеющийся внутри микроконтроллера. Любой МК имеет еще
память данных. Ее принципиальное отличие от памяти
программ состоит в том, что микроконтроллер может не только
читать содержимое ее ячеек, но и определенными своими
командами содержимое их изменять (записывать в них данные),
в то время как менять содержимое памяти программ ему «не по
зубам». В силу описанной причины память данных еще иногда
называют оперативной памятью (оперативным запоминающим
устройством или ОЗУ), в отличие от памяти программ,
именуемой постоянным запоминающим устройством (ПЗУ).
10


Кроме того, стоит отметить, что занесенные в ОЗУ коды
теряются (т. е. изменяются произвольным образом) при
выключении питания.
Последний находящийся внутри МК вид памяти, без
знакомства с которым мы не сможем двигаться дальше — это
так называемая регистровая память или регистры. Они
представляют из себя ячейки оперативной памяти, обращение к
которым контроллер осуществляет более короткими и
быстровыполнимыми командами, чем к упомянутому в
предыдущем абзаце ОЗУ. В остальном же регистры и ячейки
оперативной памяти идентичны — содержимое их теряется при
выключении питания и может быть считано или изменено при
выполнении микроконтроллером некоторых из своих команд.
Более подробно о регистрах (их названиях, адресах, командах
обращения к ним) мы будем говорить в одной из следующих
глав.
Для пояснения всего сказанного приведу следующий
пример. На рис. 1 показано содержимое последовательно
расположенных восьми ячеек памяти программ
микроконтроллера с занесенным в него куском
пользовательской программы.
На первый взгляд эта сущая бессмыслица. Но на самом деле
это -=-вполне разумный фрагмент реальной программы. Код
238 предписывает микроконтроллеру перенести (говорят
«прочитать») число из регистра R6 в главный регистр МК,
называемый аккумулятором. Код 36 предписывает контроллеру
прибавить к содержимому аккумулятора число, размещенное в
памяти программ непосредственно за этим кодом (т. е. в
данном случае — единицу). Код 254 предписывает вернуть
полученное в результате суммирования число из регистра-ак-
кумулятора в регистр R6. Следующий код (239) вынуждает МК
прочитать в аккумулятор число из регистра R7. Код 52 —
прибавить к
— через резистор R1 сопротивлением порядка сотни килоом. В
момент включения питания конденсатор разряжен, и вход
сброса оказывается под потенциалом, близким к напряжению
питания. Несмотря на снижение этого потенциала вследствие
заряда СЗ, в течение нескольких десятков миллисекунд уровень
сигнала на входе сброса остается единичным, и
осуществляется корректный запуск микроконтроллера.
Как-то раз в одной из моих плат вследствие ошибки в
разводке вход сброса оказался висящим в воздухе —
упомянутая RC-цепь была соединена с соседней ножкой. В
результате мне понадобилось несколько дней для того, чтобы
понять, почему при включении питания контроллер то
запускался нормально, то «зависал», не подавая никаких
признаков жизни. Я запрограммировал второй контроллер,
вставил вместо первого — то же самое, разве что после этого
один нормальный старт стал приходиться не на четыре
«зависания», а на три. В общем, если микроконтроллер с
отлаженной программой то нормально стартует, то ведет себя
кое-как, начните с проверки цепи сброса.
Следующий важный вход — ЕА, ножка 31. Если на него по
ана логическая единица, то МК работает с уже упоминавшейся
памятью программ, расположенной на кристалле. Нуль на входе
ЕА заставит микроконтроллер выполнять программу из внешней
памяти (такое возможно). О том, как организовывается связь
между МК и дополнительной микросхемой, содержащей эту
внешнюю память, мы расскажем в одной из следующих глав. На
первых же порах мы будем работать только с памятью
программ на кристалле, поэтому на входе ЕА должна быть
установлена логическая единица. Избегайте плавающего
потенциала на этом входе — если он окажется висящим в
воздухе, контроллер будет работать нестабильно, постоянно
сбоить и «зависать».
На ножке 30 (ALE) обычно присутствует непрерывная
последовательность прямоугольных импульсов с частотой, в 6
раз ниже, чем у кварцевого резонатора, соединенного с
ножками 18 и 19. Для 12-мегагерцового кварца она, очевидно,
составит 2 МГц. В этой последовательности длительность
единицы на ножке ALE примерно вдвое меньше длительности
нуля, т. е. скважность составляет 33 %. Этот сигнал можно
использовать для тактирования микросхем, требующих для
работы внешний источник тактового сигнала.
Назначение ножки 29 (PSEN) будет рассмотрено в разделе,
где мы будем говорить о подключении к МК внешней памяти.
Оставшиеся 32 ножки — это линии ввода/вывода
информации. Они сгруппированы по 8 в четыре так называемых
порта ввода/вывода (РО, Р1, Р2 и РЗ). Каждая линия любого из
них может использо-
14


ваться либо как вход, либо как выход, независимо от
использования остальных линий. Для этого их оконечные
каскады выполнены соответствующим образом. На рис. 3
приведена упрощенная схема одной из линий ввода/вывода
порта Р1.
Как видно из рис. 3, д, ножка микросхемы Pl.x соединена со
стоком выходного полевого транзистора VT1, «подтянутого» к
потенциалу питания при помощи внутреннего нагрузочного
резистора R. Одновременно с этим, с этой же ножкой
микросхемы соединен вход буфера ввода D2. Если мы
присоединим к этой ножке микросхемы через резистор К^.базу
внешнего транзистора VT2, то занося в триггер-защелку DO
логические 1 или О, мы будем открывать или закрывать VT2,
реализуя, таким образом, выбранную линию в качестве линии
вывода информации.
Использование линии в качестве линии ввода информации
иллюстрируется рис. 3, б. Ножка микроконтроллера (а,
следовательно, и вход буфера D2) соединены с выходом
микросхемы D^ ^^, состояние которого мы хотим
проанализировать (иными словами, «ввести» его в МК, или
«прочитать»). Но прежде, чем читать содержимое буфера D2,
необходимо закрыть транзистор VT1, записав в триггер DO этой
линии единицу. В самом деле, если VT1 будет открыт, он
попросту шунтирует анализируемый выход микросхемы D^^ . В
лучшем случае этот конфликт на ножке просто исказит
вводимую информацию, когда выход микросхемы D будет
«тянуть» потенциал вверх, а выход VT1 — вниз. В худшем же
варианте победа сильнейшей из противоборствующих сторон
приведет к сгоранию слабейшей. Так что запомним, что если
какие-то линии порта мы собираемся использовать в качестве
линий ввода, то перед этим обязательно в соответствующие
выходные триггеры нужно записать единицы.
По схеме, приведенной на рис. 3, выполнены линии портов
Р1, Р2 и РЗ. Порт РО оформлен несколько иначе — сток его
транзистора VT1 вместо обычного нагрузочного резистора
соединен с динамической нагрузкой (источником тока). Это
сделано для того, чтобы линии порта РО при занесении в их
триггеры-защелки единичек оказывались в так называемом
высокоимпедансном («сером») состоянии, характеризующимся
очень высоким выходным сопротивлением. В остальном же
функционирование линий порта РО похоже на работу линий
остальных трех портов.
ДВОИЧНЫЕ И ШЕСТНАДЦАТЕРИЧНЫЕ ЦИФРЫ
Приведенной выше информации по цоколевке и назначению
выводов вполне достаточно для рассмотрения первых
примеров.
15


Однако прежде, чем перейти к ним, нам необходимо
познакомиться с некоторыми особенностями записи программ и
чисел.
При написании программ нам с вами придется пользоваться
так называемыми двоичным и шестиадцатеричным
представлением чисел. Поначалу привыкнуть к ним довольно
трудно. Но после привыкания числа в двоичном и
шестнадцатеричном представлении дают вам при анализе и
написании программ гораздо больше информации, чем
привычные десятичные числа.
В двоичном представлении числа записываются при помощи
всего двух цифр — О и 1. Числам О и 1 в двоичном
представлении соответствуют, как и обычно, цифры О и L А вот
с двойкой уже иначе—число 2 в двоичной системе
записывается как 10В (буква В на конце служит признаком того,
что число записано в двоичном представлении; просто
10 без буквы на конце или 10D — это десять, а 10В — это
двойка).
Почему двойка в двоичном представлении записывается
таким образом? Да вот почему. В обычной арифметике для
первых десяги чисел (от нуля до девяти) есть десять цифр — О,
1, 2,..., 9. Для следующего числа, десятки, самостоятельной
цифры уже нет. Поэтому для нее мы снова используем самую
младшую цифру О, но ставим слева перед ней цифру 1 —
число становится двузначным. Так и с двойкой в двоичном
представлении — на нуль и единицу есть свои цифры, а на
двойку уже нет (напомню, есть только цифры О и 1). Поэтому,
как и десятка в обычном представлении, двойка в двоичном
представлении записывается при помощи младшей из двух
возможных цифр — О, слева перед которым ставим I.
Тройка в двоичной системе представляется как ИВ—это
очевидно,
11 — это число, на 1 больше, чем 10. А вот число четыре двумя
цифрами
в двоичной системе не представить. Действительно, ООВ (или
ОВ) соот
ветствует нулю, О IB (или IB) соответствует единице, 10В
соответствует
двойке, 11В — тройке, а пятого двузначного числа, используя
только
цифры О и 1, записать нельзя. Как быть? Элементарно. Коль
скоро все
двузначные числа кончились, четверка будет трехзначной,
причем пра
вые две цифры должны быть нулями, а крайняя слева — 1. Т. е.
четыре
в двоичном представлении — это ШОВ, пять — соответственно
10IB,
шесть — 110В, семь — 111В. Для восьмерки и трех цифр уже не
хвата
ет — значит, она должна быть четырехзначной, три правых
цифры —
нули, крайняя слева какая? Правильно, 1. Итого, восемь — это
1000В.
Надеюсь, принцип представления чисел в двоичной системе
я вам объяснил. Если нет — вам придется обратиться к
примерам, приведенным в Приложении 1. Ну а чтобы легко
переводить числа из двоичного представления в десятичное и
наоборот, проще всего использовать стандартную программу
Калькулятор из Windows (кнопка «Пуск» — меню
17



«Программы» — меню «Стандартные» — «Калькулятор»).
Запустив «Калькулятор», щелкните мышью заголовок меню «Вид»,
выберете «Инженерный». Слева над цифрами вы увидите
форму представления числа — Hex (шестнадцатеричное, это у
нас впереди), Dec (обычное десятичное, оно всегда выбрано
при первом запуске программы Калькулятор), Oct
(восьмеричное, мы им не будем пользоваться) и Bin — двоичное
или бинарное (отсюда и буква В на конце обозначения
двоичных цифр). Выбрав Dec—десятичное и набрав 8, нажмите
мышкой на Bin, и вы увидите на экране 1000 (не забудьте, что
это — 1000 в системе Bin, т. е. 1000В). Не выходя из системы Bin,
наберите 11000011 и перейдите в Dec — вы увидите на экране
195, т. е. числу 11000011 в двоичной системе соответствует 195 в
привычной нам десятичной. И так далее...
В шестнадцатеричном представлении числа записываются
при помощи 16 цифр. Десять из них вам хорошо знакомы — О,
1, 2, ..., 9. В качестве остальных шести цифр используют (не
удивляйтесь!) буквы А, В, С, D, Е, F. То есть для числа десять
есть своя цифра (А), для числа одиннадцать — цифра В, для
числа двенадцать — цифра С, для числа тринадцать — цифра
D. Числам четырнадцать и пятнадцать соответствуют, как вы
уже догадались, цифры Е и F. А вот для числа шестнадцать
цифры уже нет, поэтому его, как и двойку в двоичном пред-
ставлении, запишем в виде младшей цифры — О, перед
которой поставим цифру 1: таким образом, число шестнадцать
— это ЮН. Буква Н в конце обозначает, что число записано в
шестнадцатеричной системе счисления (Hex). Переводить
числа из нее в десятичную систему и обратно можно с помощью
все того же Windows-Калькулятора. Например, 112ВН — это
4395 десятичное, а 8190 десятичное — это 1FFEH (не
поленитесь, проверьте!).
Зачем все это нужно? Так, память программ
микроконтроллера АТ89С1051 содержит 1024 ячейки,
АТ89С2051 содержит 2048 ячеек, АТ89С51 — 4096 ячеек, а
АТ89С52 — 8192 ячейки. В шестнадцатеричном представлении
это будет соответственно 400Н, 800Н, ЮООН, 2000Н. Для
процессора 8086, на котором собирались первые IBM-PC,
допустимо использование памяти объемом 1048576 ячеек или
ЮООООН. Согласитесь, что в шестнадцатеричном
представлении приведенные цифры легче запоминаемы.
А теперь вернемся чуть-чуть назад, к фрагменту памяти про-
грамм, который мы анализировали в первом разделе. Обычно
адреса ячеек и коды команд представляют именно в
шестнадцатеричной, а не в десятичной системе счисления. В
Нех-представлении этот фрагмент будет выглядеть следующим
образом (рис. 4).
На самом деле это ровно то же самое, что было приведено
на рис. 1 (проверьте при помощи Windows-Калькулятора). И
хотя подобная
18


Рис. 4. Пример содержимого последовательно расположенных восьми ячеек памяти
программ микроконтроллера
запись несколько менее привычна, чем та, с рис. 1, нам
придется привыкать к таким записям. Дело в том, что программа
ассемблер, о которой мы раньше уже упоминали и которая
преобразовывает написанные нами команды в понятные
микроконтроллеру коды, все «результаты своей деятельности»
выводит в соответствующие файлы именно в
шестнадцатеричном представлении.
КРАТКИЕ ВЫВОДЫ
Итак, подведем первый итог. Микроконтроллеры
представляют собой микросхемы, которые всего-навсего «от
корки до корки» выполняют программы, занесенные в них
программистами. Последние, зная, что из себя представляет
микроконтроллер, а также то, как и какие команды он может
выполнить, составили и отладили программы (последователь-
ность этих самых команд), оттранслировали их при помощи
ассемблера и занесли их в микроконтроллеры при помощи
специальных программаторов. Подобный программатор вы
должны либо найти у кого-то из знакомых, либо приобрести в
одной из фирм, рекламирующихся в журнале «КОМПОНЕНТЫ И
ТЕХНОЛОГИИ». Стоить он может от 15...20 до 200...400 у. е., в
зависимости от его универсальности. Ни в коем случае не
приобретайте программатор, не убедившись в том, что он
«шьет» интересующие нас микроконтроллеры (в частности,
фирмы Atmel семейства АТ89 — АТ89С1051, АТ89С2051,
АТ89С51, АТ89С52; они самые доступные и дешевые). Для
начала, конечно, лучше не покупать, а найти программатор у
кого-либо из знакомых. Чаще всего эти люди к тому же смогут
вам что-то подсказать при совершении вами первых шагов, что
отнюдь не лишне для начинающих.
У большинства МК память программ (ПЗУ) находится на
кристалле и состоит из нескольких тысяч ячеек. Содержимое их
не изменяется при включении/выключении питания. В каждой
ячейке хра
может быть продолжен еще на десяток строк. Однако из
всего их многообразия в данный момент нас интересуют лишь
те, которые имеют отношение к связи АЦП с МК. Вот их-то не
так уж и много.
Во-первых, с точки зрения интерфейса АЦП делятся на
параллельные и последовательные. Первые носче
преобразования передают микроконтроллеру все биты
результата одновременно, каждый по своей индивидуальной
линии. Это означает, что с 12-разрядным АЦП МК должен быть
связан минимум 12-ю проводниками (реально — на 3-5 больше
упомянутого числа за счет сигналов управления).
Последовательные АЦП связаны с микроконтроллером
всего тре-мя-четырьмя проводниками, независимо от их
разрядности. Биты результата оцифровки они передают по
одному проводнику, один за одним (последовательно).
Управление передачей микроконтроллер осуществляет по
второму проводнику, третий, как правило, дает АЦП команду на
начало преобразования. Ясно, что последовательные АЦП
работают медленнее параллельных, но достигнутые ими
предельные скорости преобразования и передачи информации
достаточно высоки (многим 12-14-разрядным АЦП требуется
менее 10 мкс на весь цикл преобразования/передачи данных).
Далее, АЦП могут содержать некоторые внутренние регистры
г, (ячейки памяти разрядностью от 4 до 24 бит), в которые
микроконт-
!i роллер должен предварительно занести информацию. К таким
АЦП-
относятся, к примеру, многоканальные преобразователи —
микроконтроллер должен сообщить АЦП, какой из каналов
последнего должен преобразовывать информацию. При работе
с такими микросхемами МК не только читает информацию, но и
записывает ее в АЦП при помощи соответствующих сигналов.
Подобные преобразователи, естественно, сложнее простых, не
требуюш,их записи в них управляю-1ЦИХ слов (заносимую
микроконтроллером в подобные микросхемы информацию
программисты обычно называют управляющими словами),
гюэтому на первом этапе мы исключим подобные сложные мик-
росхемы из нашего рассмотрения и вернемся к ним попозже.
Остальные различия не столь принципиальны — какими сиг-
налами управляется АЦП, какова полярность этих сигналов,
каковы привязки к их фронтам и спадам, задержки и т. д. Эту
информацию, имеющуюся в datasheet'e на конкретную
микросхему, вы должны держать перед собой при разработке
аппаратного сопряжения
и программы для МК.
Перейдем теперь к конкретному примеру, в качестве
которого у меня заготовлено сопряжение с МК 12-разрядного
параллельного АЦП AD7880 фирмы Analog Devices (рис. 5).
Года четыре назад это был чуть ли не единственный АЦП,
требовавший всего одно питаю-
22


щее напряжение (5 В) и потреблявший при этом относительно небольшой
ток (< 10 мА). Почему я выбрал именно этот АЦП? Да просто когда-то я с
ним работал, и программы, которые мы с вами будем анализировать,
были тогда отлажены и не содержат ошибок.
Коль скоро АЦП параллельный 12-разрядный, с микроконтроллером
он должен быть соединен 12 линиями данных, по которым в последний
будут одновременно переданы все 12 бит результата. Добавлю, что для
организации обмена АЦП AD7880 использует еще 4 сигнала управления—
CONVST, CS, RD (входы АЦП) и BUSY (выход; кстати, его можно и не
использовать, в чем мы убедимся чуть ниже). Итого, контроллер с АЦП
должны соединяться 15-ю или 16-ю проводниками. Кроме того, на вход
CLKIN АЦП нужно подать тактовую последовательность с частотой до 2,5
МГц и отношением длительности единичного уровня к длительности
нулевого в пределах от 0,4 :0,6 до 0,6: 0,4. К сожалению, хотя
рассмотренный нами ранее сигнал ALE микроконтроллера имеет
подходящую для данного случая частоту (в 6 раз меньшую, чем частота
кварцевого резонатора, т. е. 2 МГц для 12-мега-герцового кварца),
скважность этого сигнала составляет 33 %. Поэтому на вход CLKIN АЦП
нужно подать 1...2-мегагерцовую тактовую последовательность с
отдельного генератора.
Как отмечалось, АЦП с МК должны связывать 15 или 16 проводников.
Фактически мы будем вынуждены целиком задействовать два из четырех
8-разрядных порта ввода/вывода МК. В принципе мы можем использовать
для этого любые линии любых портов, но удобнее взять линии портов Р1
и Р2 или Р1 и РЗ. Как видно из рис. 5, я остановился на втором варианте,
поскольку в имеющихся у меня аппаратных средствах отладки (об этом —
позже) порты РО и Р2 заняты — они используются для связи с
компьютером.
Работает AD7880 следующим образом. В начальный момент времени
на всех ножках, соединенных со входами управления АЦП, мик-
роконтроллер должен установить единицы. Для запуска преобразования
на CONVST необходимо подать отрицательный импульс. Перепад на нем
из О в 1 при единичных уровнях сигналов на CS и RD запускает цикл
преобразования. В течение примерно 20 мкс АЦП (при тактовой частоте 2
МГц) оцифровывает сигнал и заносит его в свой выходной регистр. В это
время он удерживает нулевой уровень на своем выходе BUSY, и по
состоянию этого сигнала наш МК может определить, завершил ли АЦП
преобразование, или нет. Кстати, если мы по каким-либо причинам не
можем анализировать сигнал BUSY, можно просто отсчитать 20 мкс после
подачи сигнала старта преобразования — за это время оно завершится.
сите его командой CLR PSW. 5, и никакие действия МК не приведут
к его самостоятельному, без вашего вмешательства, сбросу.
Именно поэтому F0 называется флагом пользователя — только
пользователь может изменять состояние этого бита (флага).
Итак, мы ознакомились со всеми используемыми битами
слова состояния PSW (бит PSW.1 не используется).
АККУМУЛЯТОР, РАСШИРИТЕЛЬ АККУМУЛЯТОРА, УКАЗАТЕЛЬ СТЕКА И
МЕХАНИЗМ ВЫЗОВА ПОДПРОГРАММ
Перечисленные в этом подзаголовке три регистра, наряду с
РОИ, являются наиболее часто используемыми у МК семейства
х51. Поэтому познакомимся с ними поближе.
Регистр-аккумулятор или просто аккумулятор мы упоминали
уже не один раз. Это основной регистр МК х51. Что это
означает? То, что у нашего МК нет второго такого регистра,
содержимое которого можно было бы сложить с содержимым
любого другого регистра, переместить в него содержимое
любого другого регистра или любой ячейки памяти.
Содержимое аккумулятора можно сдвигать, побитно инвер-
тировать (все нули заменить единицами и наоборот),
анализировать, и в зависимости от его состояния выполнять те
или иные фрагменты программы. Когда мы познакомимся с
системой команд, вы убедитесь, что большинство из них так
или иначе затрагивает аккумулятор
Прежде, чем двинуться дальше, совершим еще одно
небольшое лирическое отступление. Дело в том, что далеко не во
всех МК существует один-единственный аккумулятор, играющий
столь важную роль в его архитектуре. Есть микроконтроллеры с
двумя равноправными аккуму ляторами — все действия, которые
можно сделать с одним из них, мож но сделать и с другим.
Команд обработки данных таким микроконтроллерам требуется
поменьше, чем х51, да и сами команды покороче и
выполняются побыстрее. Казалось бы, это весьма
существенное пре имущество, и благодаря ему
двухаккумуляторные МК должны были бы вытеснить
одноаккумуляторные. Ан нет. Прежде, чем выполнить те или
иные действия над данными в обоих аккумуляторах, их нужно в
тот и в другой занести. Кроме того, после выполнения операции
нужно также переписать данные из обоих аккумуляторов
обратно в память.
В одноаккумуляторной же структуре данные из памяти
нужно переносить только в один аккумулятор, ибо в качестве
источника второ го числа (второго слагаемого или сомножителя,
вычитаемого или де-, лителя) мы предпишем контроллеру
использовать ту ячейку пам5гги где это число хранится, и его не
потребуется куда-либо переносить Так что одноаккумуляторная
структура, хотя и уступает двухаккуму-ляторной в скорости
обработки данных, но превосходит ее в скорости
66


подготовки данных для обработки. Таким образом, обе они
оказываются практически эквивалентными как с точки зрения
быстродействия, так и по функциональным возможностям.
Именно поэтому ни та, ни другая архитектура не смогла
вытеснить конкурирующую. Так что имейте ввиду, что в иных,
отличных от х51 микроконтроллерах, аккумуляторов может быть
и 2, и даже 32, пусть это вас не смущает.
Как я уже сказал, в аккумулятор мы можем перенести
данные из любого регистра общего назначения (командой MOV А,
Rn, где п = 0-7) или из любой ячейки памяти. Последнее
осуществляется командой MOV А, add г, где addr, принимающий
значения от О до 255 — это адрес ячейки памяти. Мы можем
также занести в аккумулятор любое целое число из диапазона 0-
255 (командой MOV А, #data, где data = 0-255; не забудьте про
знак #, без него МК воспримет число, записанное после символа
А, не как данные, а как адрес ячейки памяти). Естественно, из
аккумулятора можно вернуть данные в любой регистр или в
любую ячейку памяти (командами MOV Rn, А и MOV addr, А
соответственно). К нему можно прибавить содержимое любого
РОН, любого 8-битного числа или любой ячейки памяти
(командами ADD А, Rn; ADD А, #data и ADD А, addr
соответственно). С тем же успехом при помощи соответ-
ствующей команды SUB В из него можно вычесть содержимое
любого РОН, любого 8-битного числа или любой ячейки памяти.
И это далеко не полный перечень того, что можно делать с
содержимым аккумулятора х51. Ни с каким другим регистром
микроконтроллера мы не в состоянии проделать всего этого.
О регистре В мы также уже упоминали — он используется
чаще всего при умножении и делении. Команда MUL АВ
осуществляет быстрое (за 4 МКС при тактовой частоте 12 МГц)
перемножение чисел, хранящихся в аккумуляторе и в регистре
В. После завершения умножения в В хранятся старшие 8 бит
результата, а в аккумуляторе — младшие 8 бит. Командой ОIV
АВ осуществляется деление содержимого аккумулятора на
содержимое регистра В, результат деления — в аккумуляторе,
остаток от деления — в В.
Отмечу, что последняя команда — довольно бестолковая,
ибо делимое в ней должно быть не более 255. А как быть, если
вам нужно разделить, например, результат измерения,
полученный при помощи уже упоминавшихся 12-разрядных АЦП
(он может быть в пределах 0-4095), например, на Ш или на 100?
Командой DIV АВ здесь не воспользуешься. Для таких случаев
мы составим соответствующие Подпрограммы, которые
позволят выполнить подобные действия. Но об этом — чуть
позже.
Еще один важный регистр — SP (Stack Pointer) или указатель
стека. Термин этот для многих из вас нов, поэтому я постараюсь
поподробнее
объяснить, что такое стек, и какова функция регистра SP. Но
прежде нам нужно понять, что такое подпрограммы, и зачем
они нужны.
В предыдугцей главе мы рассматривали сопряжение МК с
парал лельными и последовательными АЦП и анализировали
программы, которые обеспечивали считывание результата
преобразования в ре гистры R4, R5. Но если вдуматься,
ценность этих программ почти нулевая — ну считали, а дальше-
то что? Нужно хотя бы отобразить считанное. Потом, сами по
себе коды считанного результата часто неинформативны —
нам нужно мерять ток, напряжение, темпера туру и т. д. А для
получения этих величин результат считывания надо
преобразовать — на что-то умножить, с чем-то сложить. Иногда
даже провести предварительное измерение какого-то
параметра, а после дующее преобразование осуществлять с
учетом этого результата. Например, пусть мы используем наш
МК в системе, измеряющей температуру чего-то с помощью
термопары (две сваренных в одной, точке проволочки из
различных материалов; если кто не знает, что такое термопара,
и как она работает — не беда, просто следите за ходом
рассуждений). Термопара имеет такое свойство, что прежде,
чем с ее помощью что-то измерить, нужно знать температуру
тех концов входящих в нее проволочек, которые соединены с
измерительным прибором (у нас — с АЦП). Следовательно,
перед измерением с по мощью термопары мы должны каким-то
датчиком измерить темпе ратуру упомянутых концов, а затем по
известным специалистам формулам внести соответствующую
поправку в результат последующего измерения, выполняемого
при помощи самой термопары.
Вот мы и добрались до главного. Нам для получения
результата нужно делать не одно, а два измерения — сначала
измерить темпера туру концов, а затем — сигнал с термопары.
(Я сейчас не вдаюсь i подробности аппаратной реализации
такой задачи — ясно, что в со став системы должен входить
мультиплексор, переключающий АЦП с термопары на датчик и
наоборот, какие-то усилители и т. д. — это в данный момент
неважно). Важно то, что в ходе выполнения неве домой нам
пока программы термопарного измерения нам как мини мум
дважды нужно запустить АЦП на преобразование и считать его
результат. То есть получается, что содержимое программы
par_adc.a51 (или ser_adc.a51) в этой программе должно
повторяться минимум дважды! А может и трижды или
четырежды — зависит от того, что мы захотели от нашего МК, и
как мы это программно реализовали
Давайте теперь вспомним, что память программ у
микроконтроллеров не бездонная — всего несколько тысяч
ячеек. И ее всегда не хватает. А тут получается, что нам
приходится часто повторять в на шей программе одни и те же
фрагменты. Возникает законный воп


рос — нельзя ли как-то лишь однажды написать эти фрагменты,
а затем просто обращаться к ним по мере необходимости?
Радуйтесь, можно. Вы пишите этот фрагмент (программисты
его называют подпрограммой) один раз, присваиваете ему
какое-то понятное вам имя и дальше вызываете его по мере
необходимости командой LCALL. Например, программу
(пардон, теперь уже подпрограмму) измерения при помощи
параллельного АЦП вы назвали 1ZMPAR, и собираетесь
дважды использовать в своем алгоритме. Тогда ваша программа
будет выглядеть следующим образом (рис. 15):
Рис. 15. Пример программы, дважды вызывающей подпрограмму IZMPAR
В приведенном на рис. 15 фрагменте пропущены куски
программы, содержание которых для нас в данный момент не
важно. Важными являются лишь следующие аспекты.
1. В тех местах, где нам нужно вызвать подпрограмму (в
данном случае IZMPAR), мы ставим команду LCALL IZMPAR.
2. Непосредственно перед первой командой, входящей в
состав подпрограммы, мы ставим ее имя, оканчивающееся
двоеточием. Как мы помним из предыдущего, двоеточие
ставится в конце метки. Так что имя подпрограммы для нашей
программы является меткой, куда нужно перейти для того,
чтобы подпрограмму выполнить.
70


3. В конце подпрограммы стоит пока еще неизвестная нам
команда RET.
А теперь — внимание! Микроконтроллер, выполняя
программу, дошел до того места, где нужно вызывать
подпрограмму измерения IZMPAR. Здесь он встретил команду
LCALL IZMPAR, имя которой является меткой, куда ему нужно
перейти, чтобы найти коды этой так необходимой нам
подпрограммы. Перейдя туда, он начал выполнять команды
подпрограммы, вплоть до самого ее конца. А затем... Да, как вы
думаете, что будет затем?
Если кто еще не догадался — подскажу. После того, как
подпрограмма выполнена, микроконтроллер должен выполнять
команду, которая стоит в программе следующей после команды
вызова подпрограммы. Так, в программе на рис. 15 после того,
как первый вызов подпрограммы будет завершен, МК должен
будет выполнить команду MOV А, R4, а после второго — MOV А,
R5. А как, завершив подпрограмму, МК перейдет к вьшолнению
этих команд, ведь перед ними нет никаких меток, которые
подсказали бы ему, куда переходить?
Вот мы наконец и добрались до стека. Стек — это какое-то
количество ячеек памяти, в которые микроконтроллер перед
переходом на исполнение подпрограммы заносит адрес той
самой следующей команды, которую ему предстоит выполнять
после завершения подпрограммы. Как видите, и здесь все
просто. Наткнувшись на команду вызова подпрограммы LCALL,
МК сохраняет в стеке адрес следующей за ней команды и
отправляется выполнять подпрограмму. А в конце любой
подпрограммы — запомните это! — должна стоять команда
возврата RET. Как только МК доберется до нее, для него это
послужит сигналом, что подпрограмма завершена, и он,
прочитав из стека сохраненный в нем адрес следующей
команды, перейдет на ее выполнение. Таким образом, вы
можете вызывать из вашей программы интересующую вас
подпрограмму хоть сто раз, и это не приведет к безумному
раздуванию объема программы за счет сотни повторяющихся
одинаковых кусков. Подпрограмма будет написана вами всего
однажды, а везде, где нужно ее выполнить, вы поставите всего-
навсего одну команду — вызов этой подпрограммы.
Где располагается стек? В оперативной памяти
микропроцессора или микроконтроллера. А на конкретную
ячейку, где хранится адрес команды, той самой, следующей за
вызовом подпрограммы, указывает именно регистр SP. Да,
забыл сказать, адрес этой команды, следующей за вызовом
подпрограммы, обычно называют адресом возврата.
Забавно — самому регистру SP посвящен всего один абзац.
Но прежде, чем о нем упомянуть, понадобилось десять абзацев
с описа-
71



нием того, что такое подпрограммы, и каков механизм их
вызова и
возврата из них. ,
РАБОТА МК СЕМЕЙСТВА Х51 С ВНЕШНЕЙ ПАМЯТЬЮ ДАННЫХ
До сих пор мы упоминали о наличии внутри
микроконтроллера 128 ячеек памяти данных. При этом мы
подчеркивали, что она — внутренняя, т. е. расположенная на том
же кристалле, что и остальные элементы МК. Но х51 может
работать и с внешней памятью данных. Последняя
представляет собой одну или несколько самостоятельных
микросхем памяти. Чаще всего используют статическую память
с байтовой организацией объемом 2К*8 или 8К*8 бит (1К =
1024). Такие микросхемы имеют 8 выводов данных (D0-D7), по
которым осуществляется одно временная запись в микросхему
всех 8 бит в выбранную ячейку памяти или чтение 8 бит из этой
ячейки. Далее, в таких микросхемах есть 11 или 13 адресных
входов (А0-А10 или А0-А12), комбинация сигналов на которых
задает адрес ячейки, к которой мы обращаемся. Вход WE
определяет характер обращения: если на нем установлена 1, то
осуществляется чтение из выбранной ячейки; при WE = О в
ячейку будет записана информация. Вход СЕ активизирует
микросхему памяти — когда на ее входе СЕ установлена 1, она
выключена, при СЕ = О она допускает запись в нее информации
и чтение из нее записанных данных. Нулевой сигнал на входе
ОЕ включает выкодные буферы микросхемы памяти ria
пропускание информации по линиям данных D0-D7, единичный
сигнал переводит эти линии в серое состо5шие, т. е. отключает
находящиеся внутри микросхемы ячейки памяти от ее ножек.
Описываемые микросхемы изображены на рис. 16.
Кстати, обратите внимание на то, что некоторые выводы
микросхем обозначены на схеме кружками, а над их
названиями стоят черточки (СЕ, ОЕ, WR). Так в
микропроцессорной схемотехнике принято обозначать входы и
выходы, активными сигналами для которых являются
отрицательные импульсы.
Работа с подобными микросхемами должна осуществляться
следующим образом. Положим, мы хотим записать число 145D
= 10010001В в ячейку с адресом 84D = 54Н = 1010100В. Для
этого МК должен установить записываемое число на линиях
данных D0-D7 мик росхемы (D0=D4=D7=1,
D1=D2=D3=D5=D6=0), а адрес ячейки — naj адресных линиях
(А2=А4=А6=1, А0=А1=АЗ=А5=0; А7, А8 и последу ющие старшие
адреса вплоть до А10 для микросхем объемом 2К*8 или до А12
для микросхем 8К*8 также должны быть установлены в 0). Ус
тановив адресную информацию и данные, МК одновременно с
этим
72


или чуть позже должен установить О на входе WE микросхемы
(будет запись) и О на СЁ (знак того, что мы обращаемся
именно к этой микросхеме). Как только после этого на входе ОЕ
микросхемы памяти МК установит О, осуществится запись
числа 145 в ее 84-ю ячейку.
Соответственно, если мы хотим прочитать данные из все
той же, к примеру, 84-й ячейки, мы должны, как и в
предыдущем случае, установить адрес ячейки на адресных
линиях, и одновременно с этим или чуть позже установить 1 на
входе WE микросхемы (будет чтение) и О HaCi (знак того, что
мы обращаемся именно к этой микросхеме). Как только после
этого на входе ОЕ микросхемы памяти МК установит О,
осуществится чтение числа из выбранной ячейки, и оно
появится на линиях данных D0-D7 микросхемы памяти.
Сказанное поясняется временными диаграммами,
приведенными на рис. 17.
Как аппаратно осуществляется в х51 реализация
описанного алгоритма? Для того, чтобы понять это, нужно
рассмотреть, как это было сделано в более простых с точки
зрения шинной архитектуры микропроцессорах, например в
Z80.
У Z80 есть выводы трех так называемых шин — адреса,
данных и управления. Под шинами обычно понимают
некоторое количество
73


SIMB0L1 завершается, о чем свидетельствует команда RET,
идущая после четвертого вызова ACALL ВI Т.
Итак, мы уже поняли, что подпрограмма INDVIV командами
MOV А, ADOO+n заносит в аккумулятор цифры, которые
необходимо переслать в индикатор, а затем командами ACALL
S i MB0L1 осуществляет требуемую пересылку. Последнее
реализуется той частью подпрограммы S i MB0L1, которая идет
после метки S i МВ12. А что делает та ее часть, которая
размещается между этой меткой и началом подпрограммы?
Вспомним, что в двоично-десятичном представлении О —
это ООООВ, 1 — это 0001В, 2 — это 0010В и т. д. В то же время
из таблицы отображаемых символов индикатора НТ1610 (рис.
27) следует, что у него ООООВ — это пробел, а для того, чтобы
на нем отобразился О, в него нужно переслать 1010В. Таким
образом, нуль перед отображением нужно перекодировать.
Иными словами, если под программа перед отображением
встретит ООООВ, она должна заме нить его на 1010В, и только
после этого осуществить пересылку символа в индикатор.
Посмотрим, как это реализовано в подпрограмме SiMBOLI.
Первой командой я зануляю находящиеся в аккумуляторе
старшие 4 бита отображаемого числа — цифра в двоично-
десятичном представлении должна быть четырехбитной, от
ООООВ до 1001В, следова тельно, биты с пятого по восьмой
должны быть незначащими нулями. С этой командой мы уже
знакомы. А вот следующая для нас еще нова. CJNE — это
аббревиатура выражения Compare and Jump No Equal, что в
переводе означает «сравнить и перейти, если не равно». В ходе
выполнения этой команды микроконтроллер сравнивает
содержимое аккумулятора с нулем (именно с цифрой О, о чем
нам напоминает символ # перед ней), и если А не равно О, то
переходит на выполнение той части программы, которая идет
после метки S i МВ11. О том, что там -чуть позже. Пока же
отметим, что переход туда осуществляется в том случае, если
выводимое из аккумулятора в индикатор число—не нуль Ну а
если в нем нуль (который нужно перекодировать)? Тогда МК вы-
полняет команды, идущие непосредственно за CJNE А, #0, S i
МВ11:


Команда MOV А, #10 заносит в аккумулятор десятку, т. е.
именно ту цифру, которая, будучи пересланной в НТ1610,
отобразит в соответствующем его разряде ноль. Далее стоит
команда SJMP SI MB 12, предписывающая МК перейти на
выполнение программы с метки SiMB12 (а там, как нетрудно
увидеть, рассмотренная выше часть подпрограммы,
непосредственно осуществляющая вывод цифры из
аккумулятора в индикатор). Т. е., как и предлагалось, при
обнаружении перед выводом в НТ1610 нуля МК заменяет его
на 1010В, и только потом осуществляет пересылку. Ну а если в
аккумуляторе был не нуль?
Как видите, еще одно сравнение, на этот раз — с цифрой
OFH. Если содержимое аккумулятора не равно 15 (0РН=15), то
МК перейдет на выполнение программы с метки SI MB 12, т. е.
на вывод цифры из аккумулятора в индикатор. А если в
аккумуляторе было число 0FH, то мы заменим его на О,
соответствующий пробелу. Зачем это?
Конечно, этого можно было бы и не делать. Но во многих
дешифраторах, используемых, в частности, со светодиодными
семи-сегментными индикаторами (например, серии 514),
подача на вход числа OFH приводит к погасанию всех
сегментов, т. е. к отображению пробела. Если нам при работе с
таким индикатором нужно погасить то или иное его знакоместо,
достаточно в соответствующую ему ячейку памяти занести 0FH,
и при выводе этого числа в индикатор нужный разряд погаснет.
Но это при использовании индикатора с дешифратором типа
514ИД1(2). А если нам захотелось в новой разработке заменить
светодиодный индикатор на НТ1610? Тогда нам нужно либо
искать в нашей программе все те места, где для погашения
разряда индикатора мы заносим в соответствующую ячейку па-
мяти число OFH, и заменять OFH на О, либо сделать лишь
одну перекодировку — в подпрограмме вывода числа в
индикатор. Второй случай, естественно, предпочтительнее —
основная программа везде, где надо, гасит разряды индикатора
записью в ОЗУ числа OFH, и не задумывается, какой индикатор
будет подцеплен к микроконтроллеру, а все нюансы
индикатора учитывает обслуживающая его программа.
Захотели сменить индикатор — перепишите лишь подпрог-
рамму работы с ним, при этом вы наверняка сделаете меньше
ошибок. Чем перелопачивая под новый индикатор всю свою
программу. Так что привыкайте к подобной унификации, это
сэкономит вам массу времени и нервов.
107


Итак, мы познакомились со всем, что необходимо знать для
разработки аппаратного и программного сопряжения нашего МК
семейства х51 с ЖК-индикатором на основе контроллера
НТ1611 фирмы Holtek. Как я уже говорил, это самый простой
индикатор с точки зрения его сопряжения с МК. Следующим
пунктом нашей программы будет сопряжение
микроконтроллера с многоразрядным семисегментным
светодиодным индикатором, т. е. с самым простым
представителем многоразрядных знакосинтезирующих
светодиодов.
СОПРЯЖЕНИЕ СО СВЕТОДИОДНЫМИ ИНДИКАТОРАМИ ТИПА АЛС318
АЛСЗ18 — наиболее удобный для рассмотрения
многоразрядный семисегментный светодиодный индикатор.
Разобравшись с тем, как использовать его, вы без особых
усилий адаптируете рассматриваемые аппаратные и
программные средства под любой семисегментный индикатор,
будь-то панель, набранная из десятка одиночных индикаторов с
большими цифрами или малогабаритный (в 14-вы-водном
DIP'e) пятиразрядный АЛС328.
Напомню, что семисегментные светодиодные индикаторы
выпускаются либо с объединенными анодами, либо с
объединенными катодами. АЛСЗ 18 принадлежит к последним.
Его анодами обычно управляет дешифратор типа КР514ИД1.
Управление катодами можно организовать по-разному — с
использованием второго дешифратора или напрямую от
микроконтроллера. Мы рассмотрим первый вариант — он
требует использования меньшего числа выводов микро-
контроллера.
Схема сопряжения нашего МК с индикатором АЛСЗ 18
приведена на рис. 32. Она включает в себя два дешифратора
DD3 (КР514ИД1) и DD4 (К555ИД7) и половинку микросхемы с
открытым коллектором DD2 (КР155ЛЛ2). Последняя, как будет
показано ниже, управляет десятичной запятой. Естественно,
вполне возможно вместо всех этих трех микросхем использовать
запрограммированную соответствующи\^ образом ПЛИС,
которая «вберет» их в себя и будет выполнять все свои функции
совершенно идентично тому, как это сделали бы три пере-
численные микросхемы. Но при этом потеряется ясность,
почему мы именно так, а не иначе построили нашу программу
связи МК с инди катором, и как изменить программу, если что-то
изменено в схеме со пряжения. Поэтому я и рассматриваю
схему на дискретных элемен тах, пусть даже несколько
архаичную, но наиболее удобную для первоначального
знакомства. А разобравшись с ней, вы будете делать то, что
для вас легче, проще, элегантнее — когда знаешь, что и как
сделать, придумать десяток вариантов на любой вкус несложно.
108


Итак, рассмотрим схему на рис. 32. Для работы с
индикатором используем порт Р1. Четыре его младшие линии
(Р1.0-Р1.3) выводят на дешифратор DD3 код отображаемой
цифры: ООООВ — 0; 0001В — 1;...; 1001В — 9. Выходы
дешифратора DD3 соединены с одноименными входами
индикатора (соответствующими анодами сегментов). Катоды
сегментов каждого разряда, как упоминалось, объединены
внутри индикатора и управляются выходами второго
дешифратора (DD4). На информационные входы последнего
поступают сигналы с трех старших линий порта Р1 (Р1.5-Р1.7).
Они позволяют управлять индикатором, имеющим до 8
индицируемых разрядов. Оставшаяся линия (Р1.4)
используется для управления десятичной запятой — разрядом
h индикатора. Установка этой линии в 1 зажигает запятую в том
разряде, катодный вывод которого установлен в О
соответствующим выходом дешифратора DD4.
Как врщите, для управления 8-разрядным семисегментным
светодиодным индикатором нам понадобилось 8 линий
вывода—весь порт Р1. В предыдущем случае, с НТ1610, линии
ввода/вывода использовались более экономно. Но ничего не
поделаешь, это плата за отсутствие внутри индикатора АЛСЗ 18
дополнительного микроконтроллера.
109


Наверное, вы уже догадались, что если вам нужно
управлять не восьми-, а 16-разрядным индикатором, вам
необходимо в качестве DD4 использовать дешифратор «4 в
16». Соответственно, для управления им понадобятся не 3, а 4
линии порта. Логичнее всего использовать для этого Р1.4-Р1.7
(внеся соответствующие изменения в приведенную ниже
программу). Ну а управление десятичной запятой, если она вам
необходима, придется осуществить по какой-либо линии
другого порта, например, по РЗ.О.
Будем считать, что перед нами стоит та же задача, что и в
предыдущем случае — отобразить подпрограммой i ZOBR на
индикаторе два четырехразрядных числа, хранящихся в
двоично-десятичном представлении во внутреннем ОЗУ МК в
ячейках памяти с адресами от AD00+3 (старший разряд первого
числа) до AD00 (младший разряд; в данном примере, как и
ранее, символическому адресу AD00 я присвоил численное
значение ЗОН, AD00+1 — соответственно 31Н и т. д.), и с
адресами от AD00+7 (старший разряд второго числа) до AD00+4
(младший разряд; AD00+4 — это 34Н, AD00+7 — соответственно
37Н). Первое число я вывожу в 4 правых разряда индикатора,
второе — в 4 левых, а в средний (индикатор-то 9-разрядный)
вывожу пробел.
Таким образом, если ячейка с адресом ЗОН содержит число
07Н, 31Н — число 02Н, 32Н — число 05Н, ЗЗН — число 04Н, а в
следующих четырех ячейках (с адреса 34Н по 37Н) хранятся
соответственно числа 08Н, ООН, 01Н и 02Н, то при запуске
подпрограммы IZOBR на индикаторе АЛС318 вы увидите (слева
направо) числа 2108 и 4527, разделенные пробелом.
А как быть, если нам нужно отобразить не 2108 и 4527, а
2,108 и 45,27? Очень просто. Вспомним, что отображаемые
цифры, хранящиеся в AD00+0-AD00+7, четырехбитные (от
ООООВ до ПИВ), а старшие 4 бита каждой из этих цифр хранят
незначащие нули. Давайте договоримся, что пятый бит в каждой
из ячеек памяти AD00+0-AD00+7 отвечает за десятичную
запятую, идущую непосредственно за соответствующей
отображаемой цифрой. В нашем примере в числе 2,108 запятая
должна отображаться вместе с двойкой, т. е. в ячейке с адре-
сом 37Н должно храниться не 02Н, а 12Н. Соответственно, в
числе 45,27 десятичная запятая идет после пятерки, т. е. она
хранится в виде единички в пятом по счету разряде числа в
ячейке 32Н — там вместо 05Н должно находиться 15Н. В
остальных же ячейках, хранящих цифры 1, О и 8 числа 2,108 и
4, 2, 7 числа 45,27, все 4 старших бита по-прежнему должны
хранить незначащие нули.
Соответственно, при отображении на индикаторе той или
иной цифры мы в соответствующей подпрограмме должны не
только ее перенести из четырех младших битов ячейки
ADOO+n в 4 младших
110


разряда порта Р1 (т. е. Р1.0-Р1.3), но и перенести также пятый
бит ячейки ADOO+n в пятый разряд Р1 (в Р1.4). Вот тогда мы
сможем на индикаторе увидеть не только 2108 и 4527, но и
2,108 и 45,27.
И еще, на что я хочу обратить ваше внимание. Как мы
договорились, дешифратор DD4 может управлять восемью
разрядами индикатора. В то же время АЛС318 — 9-разрядный,
и мы хотим, чтобы средний (пятый с любого края) разряд был
всегда погашен. Как погасить разряд, на управление которым у
нас уже нет свободного выхода дешифратора? Правильно,
подать на его катод единичный уровень. А если бы мы захотели
погасить самый старший (левый) разряд индикатора, а в
младших 8 разрядах отображать число от 00000000 до
99999999? Естественно, нужно было бы подать единичку на
катод старшего разряда, а с выходами дешифратора соединить
оставшиеся 8 катодов. Естественно, не забыв сделать
соответствующие изменения в приводимой ниже программе...
Ну а теперь перейдем непосредственно к программе IZOBR
(рис. 33).


(на рис. 51 эти драйверы символически изображены в виде
транзисторов обратной проводимости).
Схема управления содержит 28-разрядный внутренний
сдвиговый регистр с последовательным входом и
последовательным и параллельными выходами, причем
последние, как упоминалось, управляют драйверами
втекающего постоянного тока. Драйверы, в свою очередь,
подключены к общим катодным выводам строк светоизлу-
чающих матриц. Последовательный выход сдвигового регистра
связан с последним его разрядом и может быть подключен ко
входу следующего такого же индикатора, что позволяет
создавать 8-, 12-, 16- и т. д. разрядные индикаторы.
Сдвиговый регистр снабжен также входом разрешения,
нулевой сигнал на котором отключает отображение независимо
от состояния сигналов на входах столбцов индикатора и
содержимого регистра.
Схема сопряжения HCMS-2xxx с микроконтроллером
приведена на рис. 52.
Как видите, она довольно проста. Пять линий порта Р1 (с
Р1.0 по Р1.4) управляют транзисторами прямой проводимости,
формирующими токи через столбцы матриц светоизлучающих
диодов. Шестая линия, Р1.5, передает информацию в
сдвиговый регистр индикатора. Запись информации в
сдвиговый регистр осуществляется тактовыми импульсами,
формируемыми на Р1.6. Для отключения индикатора можно
использовать еще одну линию порта (Р1.7), сигнал на которой
при разрешении отображения должен быть единичным, а при
запрещении — нулевым. Если мы не собираемся гасить
отображение нулевым сигналом на вхо-


де гашения Vb индикатора, его нужно оторвать от Р1.7 и подать
на него уровень логической единицы.
Работает наш индикатор в режиме динамической индикации.
Вначале мы должны загрузить в него 28 бит, которые определят
состояние (включен или выключен) каждого из светодиодов
выбранного столбца у всех четырех матриц. Пусть для
определенности вначале мы зажжем светодиоды первых
(крайних слева) столбцов индикаторных матриц, открыв для
этого транзистор VT1. Первый из пересланных в сдвиговый
регистр бит определит состояние нижнего светодиода левого
столбца крайней справа индикаторной матрицы, второй бит —
второго снизу светодиода этого же столбца этой же матрицы и
т. д. до 7-гО бита, задающего состояние верхнего светодиода
левого столбца правой матрицы (см. рис. 53). Кстати, если за-
несенный в сдвиговый регистр соответствующий светодиоду
бит равен 1, то светодиод при активировании столбца будет
гореть, если равен О — соответственно будет погашен.
лях страховки ограничиваем его числом 3 (при помощи команды
ANL А, #00000011 В). Затем сложим это число с адресом ячейки,
которой мы дали символическое имя EKRAN (именно с адресом, а
не с содержимым, на что указывает знак # перед именем
EKRAN). Что при этом получилось? Если в R3 перед сложением
был О, т. е. предполагался вывод в крайний справа разряд
индикатора, то после сложения в аккумуляторе будет храниться
не изменившийся адрес ячейки с именем EKRAN. Если в R3 перед
сложением была 1, т. е. предполагался вывод во второй справа
разряд индикатора, то после сложения в аккумуляторе будет
храниться адрес следуюш;ей 51чейки (EKRAN+1). Двойка в R3 даст
в аккумуляторе после сложения EKRAN+2, тройка, как нетрудно
догадаться — EKRAN+3. А ведь именно это нам и было нужно!
Далее мы сохраним этот найденный адрес в R0, а в регистр
DPTR занесем адрес начальной ячейки таблицы
знакогенератора (MOV DPTR,#TABZNAK). После этого в
аккумулятор из R2 поместим число, которое нам предстоит
отобразить. Идущая затем команда RL А осуществит, как видно
из рис. 58, сдвиг каждого бита нашего двоичного числа на одну
позицию влево, что эквивалентно удвоению числа (на рис. 58 в
аккумуляторе до сдвига было число 00000111В=7дес., а после
сдвига — 00001110В=14дес.).
Зачем это нужно? Вот зачем. Положим, отображаемая цифра —
двойка. Где в таблице знакогенератора мы должны найти
информацию для формирования ее первого, второго,..., пятого
столбцов? Вспомним, в первых гшти байтах таблицы
(TABZNAK+0... TABZNAK+4) хранятся байты для формирования
цифры О, в следующих пяти (TABZNAK+5... TABZNAK+9) — цифры 1.
Информация для первого столбца двойки хранится в ячейке
TABZNAK+10, для второго столбца — в TABZNAK+11,..., для пятого
столбца — в TABZNAK+14. В общем виде, при помощи
формулы, можно записать, что информация для п-го столбца
цифры 2 хранится в ячейке памяти программ с адресом
TABZNAK + 2*5 + п, где для первого столбца п=0, для второго
п=1, ..., для пятого п=4. Если мы хотим отобразить цифру 3, то
информация для ее п-го столбца хранится в ячейке памяти
программ с адресом TABZNAK + 3*5 + п, для цифры 7 TABZNAK
+ 7*5 +пи т. д.
Теперь вам понятно, что для нахождения байта с
соответствующей нужному столбцу отображаемой цифры
информацией мы дол-
158


жны взять адрес начала таблицы знакогенератора (#TABZNAK),
прибавить к нему умноженную на пять отображаемую цифру, и к
сумме прибавить номер п отображаемого столбца (учитывая,
что для первого столбца п=0, для второго п=1, ..., для пятого
п=4). Теперь вернемся чуть назад, к команде RL А, которая, как
мы говорили, увеличила вдвое выводимое на индикатор число.
Еще одно выполнение этой команды учетверило это число, а
прибавление к нему содержимого регистра R2, где по-прежнему
хранится предназначенная для отображения цифра, привело к
тому, что в аккумуляторе у нас оказалось число, ровно в пять
раз большее, чем отображаемая цифра из R2. Собственно, ведь
именно это нам и нужно! Теперь осталось лишь прибавить к
аккумулятору командой ADD А, COUNSK содержимое ячейки
COUNSK (в ней должен храниться номер отображаемого
столбца, причем именно в нужном нам виде — О, 1, 2, 3,4), и мы
располагаем всем, что надо для нахождения информации о
первом столбце отображаемой цифры.
Стоящая далее команда MOVC A,@A+DPTR выполняет
следующее действие: она складывает содержимое аккумулятора
и DPTR, находит ячейку памяти программ с адресом, равным
полученной сумме, извлекает из нее информацию и помещает
извлеченное в аккумулятор. Но вспомним, в DPTR у нас
хранился адрес начала таблицы знакогенератора (#TABZNAK), а
в аккумуляторе — умноженная на пять отображаемая цифра с
прибавленным к ней номером п отображаемого столбца!
Следовательно, после выполнения команды MOVC A,@A+DPTR
мы получим в аккумуляторе байт с информацией о первом
столбце отображаемой цифры. И дальше командой MOV @RO,A
перешлем его в одну из ячеек EKRAN-EKRAN+3, ту, адрес которой
мы сохранили в R0, и которая соответствует содержавшемуся в
R3 номеру разряда индикатора, где мы хотели отобразить наш
символ.
Поздравим себя с успехом — мы сформировали-таки массив
данных, который нужно вывести в сдвиговый регистр
индикатора, чтобы отобразить первую колонку этого символа.
Осталась малость — вывести информацию в сдвиговый
регистр. По сравнению с тем, что мы делали для формирования
этого массива, вывод его в индикатор достаточно прост, что
следует из рис. 59 и рис. 60.


I


•;-





сации характерен для большинства команд, работающих с
аккумулятором и с регистрами общего назначения.
У микроконтроллеров некоторых других семейств
встречается гораздо большее количество способов адресации.
Но и перечисленных четырех вполне достаточно для того,
чтобы заставить наш МК выполнять любые нужные нам
действия (в пределах его быстродействия и допустимых
электрических параметров). В этом мы в очередной раз
убедимся в следующей главе.


ГЛАВА 6
ТАИМЕРЫ-СЧЕТЧИКИ
И СИСТЕМА ПРЕРЫВАНИЙ
МИКРОКОНТРОЛЛЕРОВ Х51


я как-то уже говорил, что знакомство с любым
микроконтроллером предполагает изучение четырех связанных с
ним аспектов знаний. К ним относятся типовые схемы его
включения, организация его памяти и регистров, организация
внутренних периферийных устройств МК с системой их
обслуживания и система команд микроконтроллера. Деление
довольно условное, но оно имеет право на существование. Так
вот, мы с вами подробно ознакомились с первым, вторым и
четвертым из этих аспектов, и этого уже достаточно, чтобы
самостоятельно начать писать программы. Скажу даже больше,
к моменту, когда я впервые начал использовать в своих
программах внутренние периферийные устройства, я написал
уже довольно много различных программ, вполне обходясь без
таймеров-счетчиков, системы прерываний или
последовательного порта. Так что не без оснований утверждаю,
что полученных вами на настоящий момент знаний достаточно,
чтобы приступить к самостоятельному конструированию
относительно простых систем на основе микроконтроллеров
семейства х51 или, вооружившись этими знаниями, начать
знакомиться с какими-нибудь другими микроконтроллерами по
не очень-то рассчитанной на начинающих книжке.
Но не стоит торопить события и ставить точку в процессе
нашего с вами знакомства с микроконтроллерами. В состав МК
входят обычно еще некоторые дополнительные устройства,
которые при правильном их использовании не только облегчают
решение поставленных задач, но иногда даже помогают
справиться с задачами, которые без их применения попросту
неразрешимы. Поэтому, прежде чем обратиться к описанию
нескольких законченных примеров прак-
Ну вот я, кажется, обосновал перед вами если не
необходимость, то желательность наличия таймера-счетчика
внутри микроконтроллера и показал в общих чертах один из
вариантов его применения. Надеюсь, все сказанное оказалось
понятным. Если же это не так, не поленитесь, пробегите еще
раз глазами последние 8 абзацев — с пониманием сказанного
идти дальше будет намного легче.
Теперь настала пора рассказать про таймеры-счетчики
поподробнее. Начну с того, что в любом МК семейства х51 их как
минимум по 2 (а в некоторых и по 3, но рассмотрение третьего,
имеющегося не везде, выходит за рамки настоящей главы).
Упомянутая «обязательная» пара таймеров-счетчиков почти
идентичная, за исключением некоторых не больших отличий, о
которых я ниже кратко упомяну. Каждый из счет чиков может
работать в 4-х различных режимах, а также считать как
мегагерцовые импульсы (с выхода делителя тактовой частоты
на 12 так и импульсы, поступающие на соответствующий вывод
микроконт роллера. Кстати, отсюда и его название таймер-счетчик:
когда на его вход поступают сигналы извне, то это — счетчик
внешних импульсов, а когда с тактового генератора — то
таймер, формирующий, как было описано, те или иные задержки.
В любой из таймеров-счетчиков можно перед началом работы
занести число от О до OFFFFH, а также любой из них можно
программным путем остановить или запустить.
Упрощенная структура таймера-счетчика С/ ТО (второй
носит название С/ТО) приведена на рис. 73.
В его состав входят два регистра, TLO (младший) и ТНО
(старший); упомянутый предделитель Д тактовой частоты на 12;
коммутатор К, подключающий вход таймера-счетчика либо к
выходу предделителя, либо ко входу ТО микроконтроллера;
выключатель1 S, управляемый от логического узла,
содержащего три простых двухвходовых логических элемента
D1...D3; флаг переполнения счетчика TFO,
214
Й


Те, кто внимательно следит за моим повествованием, могут
спросить: а что это за вход такой ТО? Мы ведь до сих пор ни
словом о нем не обмолвились, хотя описали все 40 выводов
микроконтроллера. Отвечаю. Да, действительно, описывая выводы
МК, мы говорили о 32 линиях портов РО-РЗ, двух выводах для
подключения кварцевого резонатора XTAL1 и XTAL2, входах
сброса RESET и разрешения доступа к внешней памяти программ
ЕА, выходах ALE и PSEN, а также о выводах, соединяемых с
землей и с питанием. Я только что еще раз перечислил все 40
выводов, так и не упомянув при этом ни ТО, ни Т1. Так где же они?
Когда разработчики МК х51 создавали свое детище, перед ними
стоял вопрос—уместить микросхему в 40-выводном корпусе, от
чего-то отказавшись или использовать корпус с 48 (или 64!)
выводами. Тогда, на заре эпохи микроконтроллеров, 48- и 64-
выводные корпуса были безумно дороги, гораздо дороже 40-
выводных, и разработчикам приходилось буквально впихивать все
задуманное в «сороконожку». А для этого нужно было делать
выводы микросхемы многофункциональными. Вспомните, по
линиям портов РО и Р2 при работе с внешней памятью программ и
данных выдается адресная информация, К тому же линии порта РО
еще играют роль шины данных (т. е. выполняют даже не две, а три
различные функции!). Так что думаю, что вас не очень удивит, если
я скажу вам, что функции входов ТО и Т1 микроконтроллера «по
Совместительству» выполняют линии Р1.4 и Р1.5 порта Р1. Как
принято писать в литературе, посвященной микроконтроллерам
семейства х51, линии порта Р1 могут выполнять альтернативные
функции: Р 1.4 и Р 1.5 — входы Т0иТ1;Р1.6иР1.7 — соответственно
выходы WR и RD при работе с внешней памятью данных; Р1.0 и
Р1,1 — линии RxD и TxD последовательного порта (о нем речь еще
впереди) и Р1,2, Р1-3 — входы внешних прерываний INTO и INT1
(об этом также чуть позже). Если вы хотите, чтобы вывод выполнял
соответствующую ему альтернативную функцию, запишите в
регистр-защелку соответствующей линии порта единицу и
пользуйтесь альтернативной функцией себе на здоровье, пока не
надоест.
Ну вот мы с вами попутно и осилили альтернативные функции
порта Р1. Пойдем дальше. Регистры TLO и ТНО (ТЫ и ТН1) — это
регистры самого таймера-счетчика. В них мы заносим число,
предустанавливающее таймер-счетчик, из них мы считываем его
текущее значение. Адреса этих регистров — 8АН и 8СН для TLO и
ТНО соответственно, 8ВН и 8DH для TL1 и ТН1,
Управление же таймерами-счетчиками осуществляется при по-
мощи битов, входящих в состав регистров TMOD (его адрес —
89Н)
215


Во втором режиме (М1=0, М0=1) как Т/СО, так и Т /С1
работа-! ют следующим образом. Каждый из импульсов,
поступающих на вход Т /Сх, увеличивает на 1 содержимое
регистра TLx. Когда происходит переполнение последнего (т. е.
содержимое TLx меняется с OFFH на 0), происходит увеличение
на 1 регистра ТНх. Естественно, увеличение TLx и ТНх
возможно только тогда, когда TRx установлен в 1 и INT = 1 при
соответствующем GATE=0.
Когда происходит переполнение ТНх (т. е. содержимое ТНх
меняется с OFFH на 0), то устанавливается в 1 соответствующий
флаг переполнения таймера TFx. Здесь я чуть забегу вперед и
скажу, что если написанная нами программа к этому моменту
разрешила так называемые прерывания, то установка в 1
упомянутого флага авто- ' матически вызовет соответствующую
подпрограмму, именуемую подпрограммой обработки данного
прерывания. Именно этот процесс я и имел ввиду, когда
говорил, что таймер-счетчик может сообщить нашему МК о том,
что он досчитал до OFFFFH — микроконтроллеру не нужно
постоянно контролировать состояние таймера-счетчика,
переполнение последнего вызывает соответствующую
подпрограмму автоматически, вне зависимости от того, чем в
этом момент занят микроконтроллер.
В третьем режиме (М1=1, М0=0) Т /СО и Т /С1 также
работают одинаково. Каждый из импульсов, поступающих на
вход Т /Сх, увеличивает на 1 содержимое регистра TLx. Когда
происходит переполнение последнего (т. е. содержимое TLx
меняется с OFFH на 0), то устанавливается в 1 соответствующий
флаг переполнения таймера TFx. Одновременно с этим
происходит занесение в TLx числа, хранящегося в регистре ТНх,
при этом содержимое ТНх остается неизменным (рис. 76). И в
этом случае установка в 1 соответствующего флага
переполнения таймера TFx вызывает переход на выполнение
подпрограммы обработки прерывания (если, естественно,
прерывания были разрешены; о том, как это делается, будет
сказано чуть ниже).
218


Вот, собственно, и все, что я хотел сказать о таймерах-
счетчиках. Осталось, разве что, добавить, что занесение
информации в упомянутые регистры должно осуществляться
при помощи команд MOV TLO, #clata8, MOV THO, #data8 (и им
аналогичными для регистров таймера-счетчика!), MOV
TM0D,#clata8 и M0V TC0N,#data8. Адреса упомянутых регистров
приведены выше. Если ваш ассемблер, подобно моему TASM'y>
не знает об их существовании, эти адреса нужно объявить в
разделе адресов и символических имен в начале программы.
Кроме того, биты регистра TCON входят в битовое адресное
пространство МК семейства х51, следовательно, их можно
устанавливать и сбрасывать командами SETB и CLR.
Практический пример использования таймера-счетчика мы
рассмотрим ниже, после знакомства с системой прерывания
нашего МК.
СИСТЕМА ПРЕРЫВАНИЙ МИКРОКОНТРОЛЛЕРОВ СЕМЕЙСТВА х51
В предыдущем разделе мы уже упомянули о том, что
переполнение таймеров-счетчиков может (в определенных
условиях) вызвать соответствующую подпрограмму, называемую
подпрограммой обработки прерывания от таймера-счетчика.
Настала пора подробнее познакомиться с системой прерываний,
узнать, какие еще подпрограммы обработки прерываний могут
входить в нее, как разрешить их вызов и т. д.
Однако прежде, чем это сделать, еще раз давайте обсудим
саму идеологию прерываний. Как было уже сказано, во многих
задачах мы не можем себе позволить, чтобы микроконтроллер
отвлекался от выполнения основного алгоритма, проверяя,
произошло то или иное событие (пришел ожидаемый импульс,
таймер досчитал до OFFFFH и т. д.). И в то же время нам
необходимо, чтобы контроллер как-то реагировал на упомянутые
события. Типичный пример — быстродействующий
измерительный прибор, снабженный клавиатурой. С одной
стороны он должен не отвлекаясь производить запланирован-
ные измерения. С другой же стороны, надо реагировать на
нажатие клавиш — переключать режимы, коэффициенты и т. д.
А как узнать о том, что та или иная клавиша нажата, если нет
возможности отвлекаться от основной программы через каждые
15—20 мс и проверять, есть ли нажатие?
Выход из создавшейся ситуации нашли разработчики самых
первых микропроцессоров. Они снабдили свои изделия входами,
появление на которых оговоренного в спецификации сигнала (ну,
например, импульса отрицательной полярности) приводило к
автоматическому вызову расположенной строго в определенном
мес-
 
Сайт управляется системой uCoz